package org.easy.core.util;


import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.UUID;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;


public class ZipUtil {
    public static final String TMP_DIR = System.getProperty("java.io.tmpdir");

    /**
     * 级联删除目录
     * <p>
     * {@link com.link.util.FileUtil#deleteDirs}
     * <p>
     * 这里有几个关于删除目录的相似方法在FileUtil中也有，
     * 不过只有这个是公共方法，所以没有删
     */
    @Deprecated
    public static boolean deleteDirs(File... dirs) {
        for (File dir : dirs) {
            FileUtil.deleteDirs(dir);
        }
        return true;
    }


    /**
     * zip压缩
     *
     * @param plainZipDir           待压缩的目录
     * @param newZip                压缩后的zip名字（不带后缀名,不带路径）
     * @param needDeletePlainZipDir 压缩完成后，是否删除待压索的文件
     */
    public static String zip(String plainZipDir, String newZip, boolean needDeletePlainZipDir) {
        return zipSpecifyFile(plainZipDir, newZip, new HashMap<>(), needDeletePlainZipDir);
    }

    /**
     * 压缩指定文件夹内的指定文件
     *
     * @param plainZipDir           指定的文件夹
     * @param newZip                压缩后的文件名称
     * @param nameMap               实际文件名与指定的文件的映射
     * @param needDeletePlainZipDir 压缩后是否需要删除原文件
     * @return 压缩后的文件名称
     */
    public static String zipSpecifyFile(String plainZipDir, String newZip, Map<String, String> nameMap, boolean needDeletePlainZipDir) {
        if (!FileUtil.getSuffixName(newZip).equalsIgnoreCase("zip")) {
            newZip += ".zip";
        }

        File plainZipDirFile = new File(plainZipDir);
        File zipFile = new File(plainZipDir + newZip);
        if (zipFile.exists()) {
            throw new RuntimeException("压缩包已存在");
        }
        // 一级目录内所有文件
        File[] subFiles = plainZipDirFile.listFiles();
        try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile.toPath()), StandardCharsets.UTF_8)) {
            for (File file : Objects.requireNonNull(subFiles)) {
                // 循环一级目录内的文件，递归压缩到zipOut
                recursiveZip(zipOut, file, "", nameMap);
            }
            // 结束压缩流写内容
            zipOut.finish();
            // 返回压缩包路径 是否有点多余？这两个参数都是上级方法传过来的，现在不好改了
            return plainZipDir + newZip;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 根据参数删除文件
            if (needDeletePlainZipDir && subFiles != null) {
                deleteDirs(subFiles);
            }
        }
        return "";
    }

    /**
     * 压缩文件
     * <p>
     *
     * @param zipOut      输出流
     * @param inputStream 输入流
     * @param subPath     文件夹目录
     * @throws IOException
     */
    @Deprecated
    public static void zip(ZipOutputStream zipOut, InputStream inputStream, String originalFilename, String subPath) throws IOException {
        subPath = FileUtil.qualifyPath(subPath);

        try (InputStream in = inputStream) {
            zipOut.putNextEntry(new ZipEntry(subPath + originalFilename));
            FileUtil.outputStream(in, zipOut);
            zipOut.closeEntry();
        }
    }

    /**
     * 输入流读到输出流
     * 并且关闭输入流
     *
     * @param inputStream
     * @param outputStream
     * @throws IOException
     */
    public static void outputStream(InputStream inputStream, OutputStream outputStream) throws IOException {
        try (InputStream in = inputStream) {
            final int maxSize = 2048;
            int readBytes;
            byte[] buffer = new byte[maxSize];
            while ((readBytes = in.read(buffer, 0, buffer.length)) != -1) {
                outputStream.write(buffer, 0, readBytes);
            }
            outputStream.flush();
        }
    }

    /**
     * 解压zip文件
     *
     * @param
     * @param
     * @return true/false
     */
    public static boolean unZip(String zipPath, String descDir) {
        File pathFile = new File(descDir);
        if (!pathFile.exists()) {
            pathFile.mkdirs();
        }
        // 指定编码，否则压缩包里面不能有中文目录
        try (ZipFile zip = new ZipFile(new File(zipPath), StandardCharsets.UTF_8)) {
            for (Enumeration entries = zip.entries(); entries.hasMoreElements(); ) {
                ZipEntry entry = (ZipEntry) entries.nextElement();
                String zipEntryName = entry.getName();
                try (InputStream in = zip.getInputStream(entry)) {
                    String outPath = (descDir + zipEntryName).replace("/",
                            File.separator);
                    // 判断路径是否存在,不存在则创建文件路径
                    File file = new File(outPath.substring(0,
                            outPath.lastIndexOf(File.separator)));
                    if (!file.exists()) {
                        file.mkdirs();
                    }
                    // 判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压
                    if (new File(outPath).isDirectory()) {
                        continue;
                    }

                    try (OutputStream out = new FileOutputStream(outPath)) {
                        byte[] buf1 = new byte[2048];
                        int len;
                        while ((len = in.read(buf1)) > 0) {
                            out.write(buf1, 0, len);
                        }
                    }
                }
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }


    /**
     * 创建文件夹并且获取文件流
     * 注意：需要关闭流
     *
     * @param stringInputStreamMap key=zip内文件名字,value=输入流
     * @param zipName              压缩文件名字
     * @return
     */
    public static InputStream createZipAndGetZipStream(Map<String, InputStream> stringInputStreamMap, String zipName, String plainZipDir) {
        try {
            return new FileInputStream(createZip(stringInputStreamMap, plainZipDir, zipName, false));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 把流压缩到本地目录，并且定义名字
     * 注意：需要关闭流
     *
     * @param stringInputStreamMap key=zip内文件名字,value=输入流
     * @param zipName              压缩文件名字
     * @return
     */
    public static String createZip(Map<String, InputStream> stringInputStreamMap, String zipName) {
        return createZip(stringInputStreamMap, TMP_DIR, zipName, false);
    }


    /**
     * 创建压缩文件
     *
     * @param stringInputStreamMap 文件流，String->文件名称 ，inputStream->流
     * @param plainZipDir          压缩文件目标地址
     * @param zipFileName          压缩文件名称
     * @param firstWrite           是否第一次写
     * @return 压缩文件全称
     */
    public static String createZip(Map<String, InputStream> stringInputStreamMap, String plainZipDir, String zipFileName, boolean firstWrite) {
        stringInputStreamMap.forEach((fileName, inputStream) -> {
            try (InputStream is = inputStream) {
                if (firstWrite) {
                    FileUtil.writeToLocal(is, plainZipDir + File.separator, fileName);
                } else {
                    FileUtil.writeToLocal(is, plainZipDir + zipFileName + File.separator, fileName);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        return ZipUtil.zip(plainZipDir, zipFileName, true);
    }

    /**
     * 创建压缩文件，压缩包内的文件名通过中转一次绕过编码问题
     *
     * @param stringInputStreamMap 文件流，String->文件名称 ，inputStream->流
     * @param plainZipDir          压缩文件目标地址
     * @param zipFileName          压缩文件名称
     * @return 压缩文件全称
     */
    public static String createZipEncode(Map<String, InputStream> stringInputStreamMap, String plainZipDir, String zipFileName) {
        // 文件名映射，在压缩的时候可以还原
        Map<String, String> nameMap = new HashMap<>();
        stringInputStreamMap.forEach((fileName, inputStream) -> {
            try (InputStream is = inputStream) {
                String tempFilename = UUID.randomUUID().toString();
                nameMap.put(tempFilename, fileName);
                FileUtil.writeToLocal(is, plainZipDir + File.separator, tempFilename);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        return ZipUtil.zipSpecifyFile(plainZipDir, zipFileName, nameMap, true);
    }

    /**
     * 简易压缩，将单个文件或文件夹打入压缩包内并指定名称
     * is为null则表示压缩的是文件夹
     */
    private static void simpleZip(ZipOutputStream zipOut, InputStream is, String path, String filename) throws IOException {
        if (is != null) {
            // 压缩文件
            zipOut.putNextEntry(new ZipEntry(path + filename));
            FileUtil.outputStream(is, zipOut);
        } else {
            // 压缩文件夹
            // String pathname = FileUtil.qualifyPath(path + filename);
            // 这里很奇怪，压缩空白的文件夹末尾必须以固定的 / 结尾，win和unix都是，所以这边不能拼File.separator
            // 源码 java.util.zip.ZipEntry#isDirectory
            String pathname = filename.endsWith("/") ? (path + filename) : (path + filename + "/");
            zipOut.putNextEntry(new ZipEntry(pathname));
        }
        zipOut.flush();
        zipOut.closeEntry();
    }

    /**
     * 递归压缩，并指定压缩包内文件的名称，映射不到则使用原名称
     * zipOut压缩包流，file待压缩文件，path压缩到zipOut内的路径，nameMap重命名压缩到包内的文件名
     */
    private static void recursiveZip(ZipOutputStream zipOut, File file, String path, Map<String, String> nameMap) throws IOException {
        // 合法化路径
        path = FileUtil.qualifyPath(path);

        if (file.isDirectory()) {
            // 文件夹，遍历内部文件
            File[] files = file.listFiles();
            if (files == null || files.length == 0) {
                // 空白目录，直接在压缩包内创建文件夹；递归出口
                simpleZip(zipOut, null, path, file.getName());
            } else {
                // 非空目录，遍历递归压缩
                for (File subFile : files) {
                    recursiveZip(zipOut, subFile, path + file.getName(), nameMap);
                }
            }
        } else {
            // 文件，写入到压缩包内，并检查是否需要重命名；递归出口
            String filename = nameMap.getOrDefault(file.getName(), file.getName());
            simpleZip(zipOut, Files.newInputStream(file.toPath()), path, filename);
        }
    }

    public static void main(String[] args) throws Exception {
        Map<String, InputStream> map = new HashMap<>();
        map.put("028733012_20220721103829JJחרמוני_ג'ולי_LowerJaw.stl", Files.newInputStream(Paths.get("C:\\Users\\admin\\Desktop\\test\\新建文件夹\\028733012_20220721103829JJחרמוני_ג'ולי_LowerJaw.stl")));
        map.put("028733012_20220721103829JJחרמוני_ג'ולי_UpperJaw.stl", Files.newInputStream(Paths.get("C:\\Users\\admin\\Desktop\\test\\新建文件夹\\028733012_20220721103829JJחרמוני_ג'ולי_UpperJaw.stl")));

        String plainZipDir = "C:\\Users\\admin\\Desktop\\test\\";
        String zipName = "aaa.zip";
        zip(plainZipDir, zipName, false);
//        createZipEncode(map, plainZipDir, zipName);
//        createZip(map, plainZipDir, zipName, true);
    }
}
